package com.camunda.workflow.camunda.provider;

import com.camunda.workflow.camunda.query.CustomGroupQueryImpl;
import com.camunda.workflow.camunda.query.CustomTenantQueryImpl;
import com.camunda.workflow.camunda.query.CustomUserQueryImpl;
import com.camunda.workflow.domain.MapStructMapper;
import com.camunda.workflow.domain.entity.SysRole;
import com.camunda.workflow.domain.entity.SysTenant;
import com.camunda.workflow.domain.entity.SysUser;
import com.camunda.workflow.mapper.*;
import com.camunda.workflow.utils.EncryUtil;
import com.mybatisflex.core.query.QueryWrapper;
import org.camunda.bpm.engine.authorization.Permission;
import org.camunda.bpm.engine.authorization.Permissions;
import org.camunda.bpm.engine.authorization.Resource;
import org.camunda.bpm.engine.authorization.Resources;
import org.camunda.bpm.engine.identity.*;
import org.camunda.bpm.engine.impl.AbstractQuery;
import org.camunda.bpm.engine.impl.NativeUserQueryImpl;
import org.camunda.bpm.engine.impl.context.Context;
import org.camunda.bpm.engine.impl.identity.ReadOnlyIdentityProvider;
import org.camunda.bpm.engine.impl.interceptor.CommandContext;
import org.camunda.bpm.engine.impl.persistence.AbstractManager;
import org.camunda.bpm.engine.impl.persistence.entity.GroupEntity;
import org.camunda.bpm.engine.impl.persistence.entity.TenantEntity;
import org.camunda.bpm.engine.impl.persistence.entity.UserEntity;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;


public abstract class CustomReadOnlyIdentityProvider extends AbstractManager implements ReadOnlyIdentityProvider {
    protected SysUserMapper sysUserMapper;
    protected SysRoleMapper sysRoleMapper;
    protected SysTenantMapper sysTenantMapper;
    protected SysUserRoleMapper sysUserRoleMapper;
    protected SysTenantUserRoleMapper sysTenantUserRoleMapper;

    public CustomReadOnlyIdentityProvider(SysUserMapper sysUserMapper, SysRoleMapper sysRoleMapper, SysTenantMapper sysTenantMapper, SysUserRoleMapper sysUserRoleMapper, SysTenantUserRoleMapper sysTenantUserRoleMapper) {
        this.sysUserMapper = sysUserMapper;
        this.sysRoleMapper = sysRoleMapper;
        this.sysTenantMapper = sysTenantMapper;
        this.sysUserRoleMapper = sysUserRoleMapper;
        this.sysTenantUserRoleMapper = sysTenantUserRoleMapper;
    }

    @Override
    public UserEntity findUserById(String userId) {
        this.checkAuthorization(Permissions.READ, Resources.USER, userId);
        SysUser sysUser = sysUserMapper.selectOneById(userId);
        return MapStructMapper.INSTANCE.toCamundaUser(sysUser);
    }

    @Override
    public UserQuery createUserQuery() {
        return new CustomUserQueryImpl(Context.getProcessEngineConfiguration().getCommandExecutorTxRequired());
    }

    @Override
    public UserQuery createUserQuery(CommandContext commandContext) {
        return new CustomUserQueryImpl();
    }

    @Override
    public NativeUserQuery createNativeUserQuery() {
        return new NativeUserQueryImpl(Context.getProcessEngineConfiguration().getCommandExecutorTxRequired());
    }

    @Override
    public boolean checkPassword(String userId, String password) {
        UserEntity user = this.findUserById(userId);
        return user != null && password != null && EncryUtil.passwdMatch(password, user.getPassword());
    }

    @Override
    public GroupEntity findGroupById(String groupId) {
        this.checkAuthorization(Permissions.READ, Resources.GROUP, groupId);
        SysRole sysRole = sysRoleMapper.selectOneById(groupId);
        return MapStructMapper.INSTANCE.toCamundaGroup(sysRole);
    }

    @Override
    public GroupQuery createGroupQuery() {
        return new CustomGroupQueryImpl(Context.getProcessEngineConfiguration().getCommandExecutorTxRequired());
    }

    @Override
    public GroupQuery createGroupQuery(CommandContext commandContext) {
        return new CustomGroupQueryImpl();
    }

    @Override
    public TenantEntity findTenantById(String tenantId) {
        this.checkAuthorization(Permissions.READ, Resources.TENANT, tenantId);
        SysTenant sysTenant = sysTenantMapper.selectOneById(tenantId);
        return MapStructMapper.INSTANCE.toCamundaTenant(sysTenant);
    }

    @Override
    public TenantQuery createTenantQuery() {
        return new CustomTenantQueryImpl(Context.getProcessEngineConfiguration().getCommandExecutorTxRequired());
    }

    @Override
    public TenantQuery createTenantQuery(CommandContext commandContext) {
        return new CustomTenantQueryImpl();
    }

    public long findUserCountByQueryCriteria(CustomUserQueryImpl query) {
        QueryWrapper wrapper = QueryWrapper.create()
//                .where(SysUser::getId).eq(query.getId())
                ;
        return sysUserMapper.selectCountByQuery(wrapper);
    }

    public List<User> findUserByQueryCriteria(CustomUserQueryImpl query) {
        QueryWrapper wrapper = QueryWrapper.create()
//                .where(SysUser::getId).eq(query.getId())
                ;
        List<SysUser> sysUsers = sysUserMapper.selectListByQuery(wrapper);
        List<UserEntity> list = MapStructMapper.INSTANCE.toCamundaUserList(sysUsers);
        return list.stream().map(m -> (User) m).collect(Collectors.toList());
    }

    public long findGroupCountByQueryCriteria(CustomGroupQueryImpl query) {
        QueryWrapper wrapper = QueryWrapper.create()
//                .where(SysUser::getId).eq(query.getId())
                ;
        return sysRoleMapper.selectCountByQuery(wrapper);
    }

    public List<Group> findGroupByQueryCriteria(CustomGroupQueryImpl query) {
        QueryWrapper wrapper = QueryWrapper.create()
//                .where(SysUser::getId).eq(query.getId())
                ;
        List<SysRole> sysRoles = sysRoleMapper.selectListByQuery(wrapper);
        List<GroupEntity> list = MapStructMapper.INSTANCE.toCamundaGroupList(sysRoles);
        return list.stream().map(m -> (Group) m).collect(Collectors.toList());
    }

    public long findTenantCountByQueryCriteria(CustomTenantQueryImpl query) {
        QueryWrapper wrapper = QueryWrapper.create()
//                .where(SysUser::getId).eq(query.getId())
                ;
        return sysTenantMapper.selectCountByQuery(wrapper);
    }

    public List<Tenant> findTenantByQueryCriteria(CustomTenantQueryImpl query) {
        QueryWrapper wrapper = QueryWrapper.create()
//                .where(SysUser::getId).eq(query.getId())
                ;
        List<SysTenant> sysTenants = sysTenantMapper.selectListByQuery(wrapper);
        List<TenantEntity> list = MapStructMapper.INSTANCE.toCamundaTenantList(sysTenants);
        return list.stream().map(m -> (Tenant) m).collect(Collectors.toList());
    }

    //memberships //////////////////////////////////////////
    protected boolean existsMembership(String userId, String groupId) {
        Map<String, String> key = new HashMap<>();
        key.put("userId", userId);
        key.put("groupId", groupId);
        return ((Long) getDbEntityManager().selectOne("selectMembershipCount", key)) > 0;
    }

    protected boolean existsTenantMembership(String tenantId, String userId, String groupId) {
        Map<String, String> key = new HashMap<>();
        key.put("tenantId", tenantId);
        if (userId != null) {
            key.put("userId", userId);
        }
        if (groupId != null) {
            key.put("groupId", groupId);
        }
        return ((Long) getDbEntityManager().selectOne("selectTenantMembershipCount", key)) > 0;
    }

    //authorizations ////////////////////////////////////////////////////
    @Override
    protected void configureQuery(@SuppressWarnings("rawtypes") AbstractQuery query, Resource resource) {
        Context.getCommandContext()
                .getAuthorizationManager()
                .configureQuery(query, resource);
    }

    @Override
    protected void checkAuthorization(Permission permission, Resource resource, String resourceId) {
        Context.getCommandContext()
                .getAuthorizationManager()
                .checkAuthorization(permission, resource, resourceId);
    }

}
